#ifndef __VECTOR3_H_INCL__
#define __VECTOR3_H_INCL__

#include <assert.h>
#include <math.h>
#include <ostream>

#include "mathbase.h"
/**
 * TODO: Add class description
 * 
 * @author   xiaoyu
 */
namespace PBVP {
	class Vector3 {
	public:
		float x, y, z;
		/// Constructor
		Vector3() {
			x = y = z = 0.0f;
		}
		///
		Vector3( const float fX, const float fY, const float fZ )
            : x( fX ), y( fY ), z( fZ )
        {  }
		///
        Vector3( const float fArray[3] )
            : x( fArray[0] ), y( fArray[1] ), z( fArray[2] )
        {  }
		///
		Vector3( float * const fp )
            : x( fp[0] ), y( fp[1] ), z( fp[2] )
        { }
		///
        Vector3( const float scaler )
            : x( scaler ), y( scaler ), z( scaler )
        {  }

		/// Destructor
		virtual ~Vector3() { }

		/// Copy constructor
		Vector3(const Vector3& vec) 
			: x(vec.x), y(vec.y), z(vec.z)
		{ }		

		/// Assignment operator
		Vector3& operator=(const Vector3& vec)
		{
			x = vec.x; y = vec.y; z = vec.z;
			return *this;
		}

		///
		inline float operator [] ( const size_t i ) const
        {
            assert( i < 3 );
            return *(&x+i);
        }

		///
		inline float& operator [] ( const size_t i )
        {
            assert( i < 3 );
            return *(&x+i);
        }
		/// Pointer accesser for direct copying
		inline float* ptr()
		{
			return &x;
		}
		/// Pointer accesser for direct copying
		inline const float* ptr() const
		{
			return &x;
		}
		///
        inline Vector3& operator = ( const float fScaler )
        {
            x = fScaler;
            y = fScaler;
            z = fScaler;
            return *this;
        }
		/// Comparison operator
        inline bool operator == ( const Vector3& vec ) const
        {
            return ( x == vec.x && y == vec.y && z == vec.z );
        }
		///
        inline bool operator != ( const Vector3& vec ) const
        {
            return ( x != vec.x || y != vec.y || z != vec.z );
        }
        // Arithmetic operations
        inline Vector3 operator + ( const Vector3& vec ) const
        {
            return Vector3(x + vec.x, y + vec.y, z + vec.z);
        }
		///
        inline Vector3 operator - ( const Vector3& vec ) const
        {
            return Vector3(x - vec.x, y - vec.y, z - vec.z);
        }
		///
        inline Vector3 operator * ( const float fs ) const
        {
            return Vector3(x * fs, y * fs, z * fs);
        }
		///
        inline Vector3 operator * ( const Vector3& rhs) const
        {
            return Vector3(x * rhs.x, y * rhs.y, z * rhs.z);
        }
		///
        inline Vector3 operator / ( const float fs ) const
        {
            assert( fs != 0.0 );
            float fInv = 1.0f / fs;
            return Vector3(x * fInv, y * fInv, z * fInv);
        }

		///
        inline Vector3 operator / ( const Vector3& rhs) const
        {
            return Vector3(x / rhs.x, y / rhs.y, z / rhs.z);
        }

		/// Unary operators
        inline const Vector3& operator + () const
        {
            return *this;
        }

		///
        inline Vector3 operator - () const
        {
            return Vector3(-x, -y, -z);
        }

		/// overloaded operators to help Vector3
        inline friend Vector3 operator * ( const float fs, const Vector3& vec )
        {
            return Vector3(fs * vec.x, fs * vec.y, fs * vec.z);
        }

		///
        inline friend Vector3 operator / ( const float fs, const Vector3& vec )
        {
            return Vector3(
                fs / vec.x,
                fs / vec.y,
                fs / vec.z);
        }

		///
        inline friend Vector3 operator + (const Vector3& lhs, const float rhs)
        {
            return Vector3(
                lhs.x + rhs,
                lhs.y + rhs,
                lhs.z + rhs);
        }

		///
        inline friend Vector3 operator + (const float lhs, const Vector3& rhs)
        {
            return Vector3(
                lhs + rhs.x,
                lhs + rhs.y,
                lhs + rhs.z);
        }
		///
        inline friend Vector3 operator - (const Vector3& lhs, const float rhs)
        {
            return Vector3(
                lhs.x - rhs,
                lhs.y - rhs,
                lhs.z - rhs);
        }
		///
        inline friend Vector3 operator - (const float lhs, const Vector3& rhs)
        {
            return Vector3(
                lhs - rhs.x,
                lhs - rhs.y,
                lhs - rhs.z);
        }
        /// arithmetic updates
        inline Vector3& operator += ( const Vector3& vec )
        {
            x += vec.x;
            y += vec.y;
            z += vec.z;

            return *this;
        }
		///
        inline Vector3& operator += ( const float fs )
        {
            x += fs;
            y += fs;
            z += fs;
            return *this;
        }
		///
        inline Vector3& operator -= ( const Vector3& vec )
        {
            x -= vec.x;
            y -= vec.y;
            z -= vec.z;

            return *this;
        }
		///
        inline Vector3& operator -= ( const float fs )
        {
            x -= fs;
            y -= fs;
            z -= fs;
            return *this;
        }
		///
        inline Vector3& operator *= ( const float fs )
        {
            x *= fs;
            y *= fs;
            z *= fs;
            return *this;
        }
		///
        inline Vector3& operator *= ( const Vector3& vec )
        {
            x *= vec.x;
            y *= vec.y;
            z *= vec.z;

            return *this;
        }
		///
        inline Vector3& operator /= ( const float fs )
        {
            assert( fs != 0.0 );
            float fInv = 1.0f / fs;

            x *= fInv;
            y *= fInv;
            z *= fInv;

            return *this;
        }
		///
        inline Vector3& operator /= ( const Vector3& vec )
        {
            x /= vec.x;
            y /= vec.y;
            z /= vec.z;

            return *this;
        }
        /// Returns the length (magnitude) of the vector.
        inline float length () const
        {
            return sqrtf( x * x + y * y + z * z );
        }
        /// Returns the square of the length(magnitude) of the vector.
        inline float squaredLength () const
        {
            return x * x + y * y + z * z;
        }
        /// Returns the distance to another vector.
        inline float distance(const Vector3& rhs) const
        {
            return (*this - rhs).length();
        }
        /// Returns the square of the distance to another vector.
        inline float squaredDistance(const Vector3& rhs) const
        {
            return (*this - rhs).squaredLength();
        }

        /** Calculates the dot (scalar) product of this vector with another.
            @remarks
                The dot product can be used to calculate the angle between 2
                vectors. If both are unit vectors, the dot product is the
                cosine of the angle; otherwise the dot product must be
                divided by the product of the lengths of both vectors to get
                the cosine of the angle. This result can further be used to
                calculate the distance of a point from a plane.
            @param
                vec Vector with which to calculate the dot product (together
                with this one).
            @returns
                A float representing the dot product value.
        */
        inline float dotProduct(const Vector3& vec) const
        {
            return x * vec.x + y * vec.y + z * vec.z;
        }

        /** Normalize the vector.
            @remarks
                This method normalizes the vector such that it's
                length / magnitude is 1. The result is called a unit vector.
            @note
                This function will not crash for zero-sized vectors, but there
                will be no changes made to their components.
            @returns The previous length of the vector.
        */
        inline float normalize()
        {
            float fLength = sqrtf( x * x + y * y + z * z );

            // Will also work for zero-sized vectors, but will change nothing
            if ( fLength > 1e-18 )
            {
                float fInvLength = 1.0f / fLength;
                x *= fInvLength;
                y *= fInvLength;
                z *= fInvLength;
            }

            return fLength;
        }

        /** Calculates the cross-product of 2 vectors, i.e. the vector that
            lies perpendicular to them both.
            @remarks
                The cross-product is normally used to calculate the normal
                vector of a plane, by calculating the cross-product of 2
                non-equivalent vectors which lie on the plane (e.g. 2 edges
                of a triangle).
            @param
                vec Vector which, together with this one, will be used to
                calculate the cross-product.
            @returns
                A vector which is the result of the cross-product. This
                vector will <b>NOT</b> be normalised, to maximise efficiency
                - call Vector3::normalise on the result if you wish this to
                be done. As for which side the resultant vector will be on, the
                returned vector will be on the side from which the arc from 'this'
                to rkVector is anticlockwise, e.g. UNIT_Y.crossProduct(UNIT_Z)
                = UNIT_X, whilst UNIT_Z.crossProduct(UNIT_Y) = -UNIT_X.
				This is because OGRE uses a right-handed coordinate system.
            @par
                For a clearer explanation, look a the left and the bottom edges
                of your monitor's screen. Assume that the first vector is the
                left edge and the second vector is the bottom edge, both of
                them starting from the lower-left corner of the screen. The
                resulting vector is going to be perpendicular to both of them
                and will go <i>inside</i> the screen, towards the cathode tube
                (assuming you're using a CRT monitor, of course).
        */
        inline Vector3 crossProduct( const Vector3& vec ) const
        {
            return Vector3(
                y * vec.z - z * vec.y,
                z * vec.x - x * vec.z,
                x * vec.y - y * vec.x);
        }

        /** Returns a vector at a point half way between this and the passed
            in vector.
        */
        inline Vector3 midPoint( const Vector3& vec ) const
        {
            return Vector3(
                ( x + vec.x ) * 0.5f,
                ( y + vec.y ) * 0.5f,
                ( z + vec.z ) * 0.5f );
        }

        /** Returns true if the vector's scalar components are all greater
            that the ones of the vector it is compared against.
        */
        inline bool operator < ( const Vector3& rhs ) const
        {
            if( x < rhs.x && y < rhs.y && z < rhs.z )
                return true;
            return false;
        }

        /** Returns true if the vector's scalar components are all smaller
            that the ones of the vector it is compared against.
        */
        inline bool operator > ( const Vector3& rhs ) const
        {
            if( x > rhs.x && y > rhs.y && z > rhs.z )
                return true;
            return false;
        }

        /** Sets this vector's components to the minimum of its own and the
            ones of the passed in vector.
            @remarks
                'Minimum' in this case means the combination of the lowest
                value of x, y and z from both vectors. Lowest is taken just
                numerically, not magnitude, so -1 < 0.
        */
        inline void makeFloor( const Vector3& cmp )
        {
            if( cmp.x < x ) x = cmp.x;
            if( cmp.y < y ) y = cmp.y;
            if( cmp.z < z ) z = cmp.z;
        }

        /** Sets this vector's components to the maximum of its own and the
            ones of the passed in vector.
            @remarks
                'Maximum' in this case means the combination of the highest
                value of x, y and z from both vectors. Highest is taken just
                numerically, not magnitude, so 1 > -3.
        */
        inline void makeCeil( const Vector3& cmp )
        {
            if( cmp.x > x ) x = cmp.x;
            if( cmp.y > y ) y = cmp.y;
            if( cmp.z > z ) z = cmp.z;
        }

        /** Generates a vector perpendicular to this vector (eg an 'up' vector).
            @remarks
                This method will return a vector which is perpendicular to this
                vector. There are an infinite number of possibilities but this
                method will guarantee to generate one of them. If you need more
                control you should use the Quaternion class.
        */
        inline Vector3 perpendicular(void) const
        {
            static const float fSquareZero = (float)(1e-06 * 1e-06);

            Vector3 perp = this->crossProduct( Vector3::UNIT_X );

            // Check length
            if( perp.squaredLength() < fSquareZero )
            {
                /* This vector is the Y axis multiplied by a scalar, so we have
                   to use another axis.
                */
                perp = this->crossProduct( Vector3::UNIT_Y );
            }
			perp.normalize();

            return perp;
        }

		/// Gets the angle between 2 vectors.
 		inline Radian angleBetween(const Vector3& dest)
 		{
 			float lenProduct = length() * dest.length();
 
 			// Divide by zero check
 			if(lenProduct < 1e-6f)
 				lenProduct = 1e-6f;
 
 			float f = dotProduct(dest) / lenProduct;
 
 			f = Math::Clampf(f, -1.0f, 1.0f);
 			return Math::ACos(f);
		}

        /** Returns true if this vector is zero length. */
        inline bool isZeroLength(void) const
        {
            float sqlen = (x * x) + (y * y) + (z * z);
            return (sqlen < (1e-06 * 1e-06));
        }

        /** As normalize, except that this vector is unaffected and the
            normalized vector is returned as a copy. */
        inline Vector3 normalisedCopy(void) const
        {
            Vector3 ret = *this;
            ret.normalize();
            return ret;
        }

        /** Calculates a reflection vector to the plane with the given normal .
        @remarks NB assumes 'this' is pointing AWAY FROM the plane, invert if it is not.
        */
        inline Vector3 reflect(const Vector3& normal) const
        {
            return Vector3( *this - ( 2 * this->dotProduct(normal) * normal ) );
        }

        // special points
        static const Vector3 ZERO;
        static const Vector3 UNIT_X;
        static const Vector3 UNIT_Y;
        static const Vector3 UNIT_Z;
        static const Vector3 NEGATIVE_UNIT_X;
        static const Vector3 NEGATIVE_UNIT_Y;
        static const Vector3 NEGATIVE_UNIT_Z;
        static const Vector3 UNIT_SCALE;

        /// Function for writing to a stream.
        inline friend std::ostream& operator <<
            ( std::ostream& o, const Vector3& v )
        {
            o << "Vector3(" << v.x << ", " << v.y << ", " << v.z << ")";
            return o;
        }
	};
}
#endif // __VECTOR3_H_INCL__
